#ifndef _m3_H_
#define _m3_H_

#include "v3.h"

class m3
{
public:
	// Note that in these functions, 'Set' has a different meaning than usual.
	// 'Set' means to initialize the matrix as described
	// A function without 'Set' implies that the existing matrix is modified as described

	// INITIALIZE A m3
  inline m3& operator=(const m3 &M);
	inline void SetYZPos(const v3 &YUp, const v3 &ZForward, const v3 &Pos);
	inline void SetIdentity();
	inline void SetTrans(float fX, float fY, float fZ);
	inline void SetTrans(const v3 &Translation);
	void SetRot(const v3 &Axis, float fRadians);
	inline void SetXRot(float fRadians);
	inline void SetYRot(float fRadians);
	inline void SetZRot(float fRadians);
	void SetLookat(const v3 &Pos, const v3 &Target, const v3 &Up);
	void SetInverse(const m3 M);

	// MODIFY a m3
	inline void Trans(float fX, float fY, float fZ);
	void Trans(const v3 &Translation);
	void Rot(const v3 &Axis, float fRadians);
	void XRot(float fRadians);
	void YRot(float fRadians);
	void ZRot(float fRadians);
	void Normalize();

	// OPERATE ON A v3
	v3 ReverseTransformVector(v3 const &V) const;	// Reverse-Transform vector by this matrix (this * vector)
	v3 ReverseRotateOnlyVector(v3 const &V) const;
	v3 RotateOnlyVector(v3 const &V) const;
    inline v3 operator*(const v3 &V) const;

	// OTHER
	m3 operator* (const m3 &M) const;
    inline float *GetAsOpengl();

	// members
	v3 x;
	v3 y;
	v3 z;
	v3 pos;
};

void m3::SetYZPos(const v3 &YUp, const v3 &ZForward, const v3 &Pos)
{
	z=ZForward;
	y=YUp;
	x=y.Cross(z);
	y=z.Cross(x);
	x.Normalize();
	y.Normalize();
	z.Normalize();
	pos=Pos;
}


void m3::SetIdentity()
{
	// Interesting but annoying C++ note:  At one time, I was setting these v3's by
	// assigning them to the OMath globals, like 'OMath::XAxis'.  But then in a
	// demo program, I had a constructor on a static variable that tried to call
	// this function, but it was happening before 'OMath' was inited, and so
	// this function ended up filling the matrix in with all zeroes.  Oh well.
	// There's at least one lesson in there somewhere.
	x.Set(1.0f, 0.0f, 0.0f);
	y.Set(0.0f, 1.0f, 0.0f);
	z.Set(0.0f, 0.0f, 1.0f);
	pos.Set(0.0f, 0.0f, 0.0f);
}


void m3::SetTrans(float fX, float fY, float fZ)
{
	v3 V;
	V.Set(fX, fY, fZ);
	SetTrans(V);
}


void m3::Trans(float fX, float fY, float fZ)
{
	v3 V;
	V.Set(fX, fY, fZ);
	Trans(V);
}


void m3::SetTrans(const v3 &Translation)
{
	x.Set(1.0f, 0.0f, 0.0f);
	y.Set(0.0f, 1.0f, 0.0f);
	z.Set(0.0f, 0.0f, 1.0f);
	pos=Translation;
}


void m3::SetXRot(float fRadians)
{
	float fCosine;
	float fSin;

	OMath::SinCos(fRadians, fSin, fCosine);

	x.Set(
		1.0f,
		0.0f,
		0.0f);
	y.Set(
		0.0f,
		fCosine,
		fSin);
	z.Set(
		0.0f,
		- fSin,
		fCosine);
	pos=OMath::ZeroVector;
}


void m3::SetYRot(float fRadians)
{
	float fCosine;
	float fSin;

	OMath::SinCos(fRadians, fSin, fCosine);

	x.Set(
		fCosine,
		0.0f,
		- fSin);
	y.Set(
		0.0f,
		1.0f,
		0.0f);
	z.Set(
		fSin,
		0.0f,
		fCosine);
	pos=OMath::ZeroVector;
}


void m3::SetZRot(float fRadians)
{
	float fCosine;
	float fSin;

	OMath::SinCos(fRadians, fSin, fCosine);

	x.Set(
		fCosine,
		fSin,
		0.0f);
	y.Set(
		- fSin,
		fCosine,
		0.0f);
	z.Set(
		0.0f,
		0.0f,
		1.0f);
	pos.Set(0.0f, 0.0f, 0.0f);
}


inline m3& m3::operator=(const m3 &M)
{
	x=M.x;
	y=M.y;
	z=M.z;
	pos=M.pos;

	return *this;
}

inline v3 m3::operator*(const v3 &V) const
{
	v3 R;

	R.x=
		x.x*V.x +
		y.x*V.y +
		z.x*V.z +
		pos.x;
	R.y=
		x.y*V.x +
		y.y*V.y +
		z.y*V.z +
		pos.y;
	R.z=
		x.z*V.x +
		y.z*V.y +
		z.z*V.z +
		pos.z;

	return R;
}


inline float *m3::GetAsOpengl()
{
    static float af[16]=
    {
        x.x, y.x, z.x, 0.0f,
        x.y, y.y, z.y, 0.0f,
        x.z, y.z, z.z, 0.0f,
        pos.x, pos.y, pos.z, 1.0f
    };

    return af;
}

#endif
